/*
MeeDocs - A Google Docs / Google Drive client for N9
Copyright 2012 Marcel D. Juhnke <marcel.juhnke@ovi.com>

This file is part of MeeDocs.

    MeeDocs is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    MeeDocs is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with MeeDocs.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "meedocs.h"


MeeDocs::MeeDocs()
 {
    // construct TransferUI client
    transferClient = new TransferUI::Client::Client(this);

    if(!transferClient->init()) {
            qDebug()<<"Cannot initialize TUIClient"; //error
            delete transferClient;
        }

     /* m_client_id = "688932937241.apps.googleusercontent.com";
     m_client_secret = "MkwxK2cUClP0pTbPD5QqA2bw";
     m_redirect_uri = "urn:ietf:wg:oauth:2.0:oob";
     m_grant_type = "authorization_code"; */
 }

 void MeeDocs::doDownload(const QUrl &url, const QString &accessToken, QString name, const QString &doctype)
 {
     // Download file at the given URL

     //connect(&manager, SIGNAL(finished(QNetworkReply*)),
       //      SLOT(downloadFinished(QNetworkReply*)));

     // Define needed HTTP headers for GDocs download request, such as the access token
     QByteArray auth_header("Bearer ");
     auth_header.append(accessToken);
     QNetworkRequest request(url);
     request.setRawHeader("Authorization", auth_header);
     downReply = manager.get(request);

     // Create entry in Transfer UI for the download
     downloadTransfer = transferClient->registerTransfer(name, TransferUI::Client::TRANSFER_TYPES_DOWNLOAD);
     downloadTransfer->waitForCommit();
     downloadTransfer->setName(name);
     downloadTransfer->setActive(0.0);
     downloadTransfer->commit();

     transferClient->showUI();

     m_download_progress_iterator = 1;
     connect(downloadTransfer, SIGNAL(cancel()), SLOT(cancelDownload()));
     connect(downReply, SIGNAL(finished()), SLOT(downloadFinished()));
     connect(downReply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(downloadStatus(qint64,qint64)));


     // Slashes and Colons in local filenames not good, therefore swap those characters for the downloaded filename
     name.replace("/", "_");
     name.replace(":", "_");

     m_filename = "/home/user/MyDocs/Downloads/";
     // m_filename = "C:/Users/marrat/";
     m_filename.append(name);

     /* if the downloaded file is a document and not a raw file, its mimetype is "text/html", but Google doesn't
       append .html to the filename automatically, hence we do it here manually */
     /* if ( doctype == "text/html") {
         m_filename.append(".html");
     } */
 }

// Self describing
 bool MeeDocs::saveToDisk(const QString &filename, QIODevice *data)
 {
     QFile file(filename);
     if (!file.exists(filename)) {

         if (!file.open(QIODevice::WriteOnly)) {

             fprintf(stderr, "Could not open %s for writing: %s\n",
                     qPrintable(filename),
                     qPrintable(file.errorString()));
             return false;
         }

         file.write(data->readAll());
         file.close();
     }

     return true;
 }


 // When the download's NetworkReply has been received without errors, save it to disk
 void MeeDocs::downloadFinished()
 {
     QUrl url = downReply->url();
     if (downReply->error()){
         fprintf(stderr, "Download of %s failed: %s\n", url.toEncoded().constData(), qPrintable(downReply->errorString()));
         downloadTransfer->markCancelled();
     } else {
         if (saveToDisk(m_filename, downReply)){
             printf("Download of %s succeeded (saved to %s)\n", url.toEncoded().constData(), qPrintable(m_filename));
             downloadTransfer->markCompleted(true, "", m_filename);
         }
     }

     downReply->deleteLater();

     downloadTransfer->commit();
     transferClient->removeTransfer(downloadTransfer->transferId());
     delete downloadTransfer;
     downloadTransfer = 0;
     m_transfer_size_set = false;
 }

// Lists the content of a given directory and return them as a StringList
QStringList MeeDocs::listDirContent(const QString &path)
{
    QStringList fileEntry;
    QDir directory;
    directory.cd(path);
    fileEntry = directory.entryList(QDir::NoFilter, QDir::DirsFirst);

    return fileEntry;
}

// Checks if a given path is a directory
bool MeeDocs::isDir(const QString &path)
{
    QDir directory;
    if (directory.cd(path)) {
        return true;
    }
    else {
        return false;
    }
}

// Upload a local file to the given URL (GData upload link)
void MeeDocs::doUpload(const QString &path, const QString &filename, const QString &accessToken)
{

    m_filename = path;
    m_filename.append(filename);

    // specifies the mimetype of the file to upload and sets upload chunk size to 512 KiB
    m_content_type = contentType(m_filename);
    m_chunk_size = 524288;

    // opens the specified file
    m_upload_file = new QFile(m_filename);
    m_upload_file->open(QIODevice::ReadOnly);

    // calculates number of chunks needed for the given upload file
    m_file_size = QByteArray::number(m_upload_file->size());
    m_file_parts = m_upload_file->size() / m_chunk_size + 1;
    m_current_file_part = 0;

    // appends access token header
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    const QString url = "https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false";

    QNetworkRequest request(url);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("Content-Length", "0");
    request.setRawHeader("Slug", filename.toUtf8());
    request.setRawHeader("X-Upload-Content-Type", m_content_type);
    request.setRawHeader("X-Upload-Content-Length", m_file_size);

    // send the first request as POST to upload link to tell the Google server we want to upload new doc

    putReply = manager.post(request, "");
    fprintf(stderr, "Sending POST to start upload");

    // after receiving a reply steer it to resumeUpload()
    connect(putReply, SIGNAL(finished()), SLOT(resumeUpload()));


}

void MeeDocs::resumeUpload()
{
    qDebug() << "Entering resumeUpload()";
    /* was only for debugging purposes

    QList<QByteArray> headerList = putReply->rawHeaderList();
    QList<QByteArray> headerValues;
    for (int s = 0; s < headerList.size(); ++s) {
        headerValues.append(putReply->rawHeader(headerList[s]));
    } */

    // asks the server which bytes it already received successfully
    m_succeeded_range = putReply->rawHeader("Range");

    // ask the server for resumable-create-media (that's where the file itself has to be uploaded to)
    if(m_next_location.isEmpty()) {
        m_next_location = QUrl::fromEncoded(putReply->rawHeader("Location"));
    }
    // if no new resumable link has been received continue using the old one
    else if(!putReply->rawHeader("Location").isEmpty()) {
        m_next_location = QUrl::fromEncoded(putReply->rawHeader("Location"));
    }
    m_current_file_part++;

    /* only for debugging
    QString error = putReply->errorString();
    QVariant http_error = putReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
    QString next = m_next_location.toString(); */

    if (putReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "201"){
        // HTTP 201 is received when all chunks have been received successfully by server
        qDebug() << "Upload finished!";
        uploadTransfer->markCompleted(false);
        uploadTransfer->commit();
        transferClient->removeTransfer(uploadTransfer->transferId());
        delete uploadTransfer;
        uploadTransfer = 0;
        m_transfer_size_set = false;
        putReply->deleteLater();
        m_upload_file->close();
        return;
    } else if (putReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "308" || putReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "200"){
        // HTTP 308 is received when chunk was sent successfully and server awaits next chunk
        qDebug() << "Uploading next chunk";
        putReply->deleteLater();
        nextUpload(m_next_location);
    } else {
        qDebug() << putReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
        emit uploadFailed();
        putReply->deleteLater();
        m_upload_file->close();
    }

}

void MeeDocs::nextUpload(const QUrl &url)
{
    // Server wants to know which bytes it will receive in next chunk, so we build the range here
    m_content_range_begin = m_chunk_size*m_current_file_part-m_chunk_size;
    m_content_range_end = m_chunk_size*m_current_file_part - 1;

    if ( m_content_range_end >= m_upload_file->size() ) {
        m_content_range_end = m_upload_file->size() - 1;
    }

    // seek to current chunk's beginning in the file
    m_upload_file->seek(m_content_range_begin);

    // Build a HTTP header for the server to tell it which byte range we will send
    QByteArray ba_content_range_begin = QByteArray::number(m_content_range_begin);
    QByteArray ba_content_range_end;
    ba_content_range_end = QByteArray::number(m_content_range_end);
    QByteArray ba_content_range_header = "bytes "+ba_content_range_begin+"-"+ba_content_range_end+"/"+m_file_size;

    qint64 content_length = m_chunk_size;
    if (m_upload_file->size() < m_chunk_size) {
        content_length = m_upload_file->size();
    }

    QNetworkRequest request(url);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("Content-Length", QByteArray::number(content_length));
    request.setRawHeader("Content-Type", m_content_type);
    request.setRawHeader("Content-Range", ba_content_range_header);

    // do a HTTP PUT to the resumable-create-media link with the current chunk as content
    qDebug() << "Uploading file part " << m_current_file_part << " of " << m_file_parts;
    putReply = manager.put(request, m_upload_file->read(m_chunk_size));

    // Create entry in Transfer UI for the upload
    m_upload_progress_iterator = 1;

    if (!uploadTransfer){
        uploadTransfer = transferClient->registerTransfer(m_filename, TransferUI::Client::TRANSFER_TYPES_UPLOAD);
        uploadTransfer->waitForCommit();
        uploadTransfer->setName(m_filename);
        uploadTransfer->setActive(0.0);
        uploadTransfer->commit();

        transferClient->showUI();

        connect(uploadTransfer, SIGNAL(cancel()), SLOT(cancelUpload()));
    }

    // do the same as long not all chunks have been successfully received by the server

    connect(putReply, SIGNAL(uploadProgress(qint64,qint64)), SLOT(uploadStatus(qint64,qint64)));
    connect(putReply, SIGNAL(finished()), SLOT(resumeUpload()));

}

void MeeDocs::uploadStatus(const qint64 &bytesSent, const qint64 &bytesTotal)
{
    m_bytes_sent = bytesSent + m_chunk_size * (m_current_file_part - 1);
    qDebug() << "Sent " << m_bytes_sent  << " of " << m_upload_file->size();

    m_upload_progress = 1.0/10*m_upload_progress_iterator;

    if (!m_transfer_size_set){
        if (uploadTransfer->setSize(m_upload_file->size())){
            uploadTransfer->commit();
        }
        m_transfer_size_set = true;
    }

    if (m_bytes_sent >= m_upload_file->size()/10*m_upload_progress_iterator){
        m_upload_progress_iterator = m_upload_progress_iterator + 1;
        uploadTransfer->setProgress(m_upload_progress);
        uploadTransfer->commit();
    }

}

void MeeDocs::downloadStatus(const qint64 &bytesReceived, const qint64 &bytesTotal)
{
    m_bytes_received = bytesReceived;
    m_bytes_total = bytesTotal;
    // qDebug() << "Received " << m_bytes_received  << " of " << bytesTotal;

    m_download_progress = 1.0/10*m_download_progress_iterator;

    if (!m_transfer_size_set){
        if (downloadTransfer->setSize(bytesTotal)){
            downloadTransfer->commit();
        }
        m_transfer_size_set = true;
    }

    if (bytesReceived >= bytesTotal/10*m_download_progress_iterator){
        m_download_progress_iterator = m_download_progress_iterator + 1;
        downloadTransfer->setProgress(m_download_progress);
        downloadTransfer->commit();
    }

    // qDebug() << "Transfer progress iterator: " << m_download_progress_iterator;
    // qDebug() << bytesTotal/5*m_download_progress_iterator << " = " << m_download_progress;
}

QByteArray MeeDocs::contentType(const QString &filename)
{
    // compares extension of given file with a list of mimetypes and returns the type if matched

    // QFile mimeFile("C:/Users/marrat/Qt/MeeDocs/qml/share/mimetypes.txt");
    QFile mimeFile("/opt/MeeDocs/qml/share/mimetypes.txt");

    if (!mimeFile.open(QIODevice::ReadOnly)) {
        return "application/octet-stream";
    }

    QTextStream extensionList(&mimeFile);
    QString mimeLine;
    QStringList extension;

    while (mimeLine != "EOF") {
        mimeLine = extensionList.readLine();
        extension = mimeLine.split(9);

        if ( filename.endsWith(extension[0]) ) {
            return extension[1].toUtf8();
        }

    }

    return "application/octet-stream";

}


void MeeDocs::cancelDownload()
{
    downReply->abort();
    // not handling downloadTransfer here, because it is handled by downloadFinished(),
    // which is called by the QNetworkManager also in an abort case!
    qDebug() << "Download has been cancelled!";
}

void MeeDocs::cancelUpload()
{
    putReply->abort();
    uploadTransfer->markCancelled();
    delete uploadTransfer;
    uploadTransfer = 0;
    m_transfer_size_set = false;
    m_upload_file->close();
    qDebug() << "Upload has been cancelled!";
}


void MeeDocs::createCollection(const QString &collectionTitle, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    QString url("https://docs.google.com/feeds/default/private/full");

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("Content-Type", "application/atom+xml");

    QByteArray xmlBody("<?xml version='1.0' encoding='UTF-8'?>");
    xmlBody.append("<entry xmlns=\"http://www.w3.org/2005/Atom\"><category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/docs/2007#folder\"/><title>");
    xmlBody.append(collectionTitle);
    xmlBody.append("</title></entry>");

    qDebug() << QByteArray::number(xmlBody.length());
    qDebug() << xmlBody;

    request.setRawHeader("Content-Length", QByteArray::number(xmlBody.length()));

    downReply = manager.post(request, xmlBody);

    connect(downReply, SIGNAL(finished()), SLOT(isCollectionCreated()));
}

// When the download's NetworkReply has been received without errors, save it to disk
void MeeDocs::isCollectionCreated()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "201"){
        // HTTP 201 is received when all chunks have been received successfully by server
        qDebug() << "Collection created";
        emit collectionCreated();
        downReply->deleteLater();
        return;
    } else {
        qDebug() << "Collection failed";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit collectionFailed();
        downReply->deleteLater();
    }

}

void MeeDocs::deleteCollection(const QString &collectionId, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    QString url(collectionId);

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");

    request.setRawHeader("If-Match", "*");

    downReply = manager.deleteResource(request);

    connect(downReply, SIGNAL(finished()), SLOT(isCollectionDeleted()));
}

// When the download's NetworkReply has been received without errors, save it to disk
void MeeDocs::isCollectionDeleted()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "200"){
        // HTTP 201 is received when all chunks have been received successfully by server
        qDebug() << "Collection deleted";
        emit collectionCreated();
        downReply->deleteLater();
        return;
    } else {
        qDebug() << "Collection delete failed";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit collectionFailed();
        downReply->deleteLater();
    }

}
void MeeDocs::addToCollection(const QString &collectionId, const QString &resourceId, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    QString url(collectionId);

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("Content-Type", "application/atom+xml");

    QByteArray xmlBody("<?xml version='1.0' encoding='UTF-8'?>");
    xmlBody.append("<entry xmlns=\"http://www.w3.org/2005/Atom\"><category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/docs/2007#folder\"/><id>https://docs.google.com/feeds/default/private/full/");
    xmlBody.append(resourceId);
    xmlBody.append("</id></entry>");

    qDebug() << QByteArray::number(xmlBody.length());
    qDebug() << xmlBody;

    request.setRawHeader("Content-Length", QByteArray::number(xmlBody.length()));

    downReply = manager.post(request, xmlBody);

    connect(downReply, SIGNAL(finished()), SLOT(isAddedToCollection()));
}

void MeeDocs::isAddedToCollection()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "201"){
        // HTTP 201 is received when the request was successful.
        qDebug() << "Document is added to collection";
        emit addedToCollection();
        downReply->deleteLater();
        return;
    } else {
        qDebug() << "Adding to collection failed";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit addedToCollectionFailed();
        downReply->deleteLater();
    }

}

void MeeDocs::removeFromCollection(const QString &url, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    qDebug() << url;

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("If-Match", "*");

    downReply = manager.deleteResource(request);

    connect(downReply, SIGNAL(finished()), SLOT(isRemovedFromCollection()));
}

void MeeDocs::isRemovedFromCollection()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "200"){
        qDebug() << "Document is removed from collection";
        emit addedToCollection();
        downReply->deleteLater();
        return;
    } else {
        qDebug() << "Removing from collection failed";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit addedToCollectionFailed();
        downReply->deleteLater();
    }

}

void MeeDocs::trashFile(const QString &selfLink, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    QString url(selfLink);

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("If-Match", "*");

    downReply = manager.deleteResource(request);

    connect(downReply, SIGNAL(finished()), SLOT(isFileTrashed()));
}


void MeeDocs::isFileTrashed()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "200"){
        qDebug() << "File moved to Trash";
        emit collectionCreated();
        downReply->deleteLater();
        return;
    } else {
        qDebug() << "Move to Trash failed";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit collectionFailed();
        downReply->deleteLater();
    }

}

void MeeDocs::shareDoc(const QString &resourceId, const QString &accessToken)
{
    // Define needed HTTP headers for GDocs API request, such as the access token
    m_auth_header = QByteArray("Bearer ");
    m_auth_header.append(accessToken);

    QString url("https://docs.google.com/feeds/default/private/full/");
    url.append(resourceId);
    url.append("/acl");

    QNetworkRequest request(url);
    request.setRawHeader("Authorization", m_auth_header);
    request.setRawHeader("GData-Version", "3.0");
    request.setRawHeader("Content-Type", "application/atom+xml");

    QByteArray xmlBody("<?xml version='1.0' encoding='UTF-8'?>");
    xmlBody.append("<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:gAcl='http://schemas.google.com/acl/2007'><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/acl/2007#accessRule'/><gAcl:withKey key='with link'><gAcl:role value='reader' /></gAcl:withKey><gAcl:scope type='default'/></entry>");

    qDebug() << QByteArray::number(xmlBody.length());
    qDebug() << xmlBody;

    request.setRawHeader("Content-Length", QByteArray::number(xmlBody.length()));

    downReply = manager.post(request, xmlBody);

    connect(downReply, SIGNAL(finished()), SLOT(isDocShared()));
}

void MeeDocs::isDocShared()
{
    if (downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "201" || downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == "409"){
        qDebug() << "ACL entry successfully updated";
        emit addedToCollection();
        downReply->deleteLater();
    } else {
        qDebug() << "Error on updating ACL entry";
        qDebug() << downReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString();
        emit addedToCollectionFailed();
        downReply->deleteLater();
    }

}

QByteArray MeeDocs::uploadFileSize(const QString &path, const QString &filename)
{

    m_filename = path;
    m_filename.append(filename);

    // specifies the mimetype of the file to upload and sets upload chunk size to 512 KiB
    m_content_type = contentType(m_filename);
    m_chunk_size = 524288;

    // opens the specified file
    m_upload_file = new QFile(m_filename);
    m_upload_file->open(QIODevice::ReadOnly);

    // calculates number of chunks needed for the given upload file
    m_file_size = QByteArray::number(m_upload_file->size());

    return m_file_size;
}

void MeeDocs::openShareUI(const QString &url)
{
    MDataUri duri;


        duri.setMimeType ("text/x-url");


        duri.setTextData (url);


        //duri.setAttribute ("title", title);


        //duri.setAttribute ("description", desc);


        if (duri.isValid() == false) {


            qCritical() << "Invalid URI";


            return;
        }


        QStringList items;


        items << duri.toString();


        //qDebug() << "URI:" << items.join (" ");


        // Create a interface object
        ShareUiInterface shareIf("com.nokia.ShareUi");


        // Check if interface is valid
        if (shareIf.isValid()) {


            // Start ShareUI application with selected files.


            //qDebug() << "Signalling share-ui daemon...";


            shareIf.share (items);


        } else {


            qCritical() << "Invalid interface";


            return;
        }

}
